package com.dbflow5.adapter.saveable;

import com.dbflow5.adapter.CacheAdapter;
import com.dbflow5.database.DatabaseStatement;
import com.dbflow5.database.DatabaseWrapper;

import java.util.Collection;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * Description: Used for model caching, enables caching models when saving in list.
 */
public class CacheableListModelSaver<T> extends ListModelSaver<T> {
    private final CacheAdapter<T> cacheAdapter;

    public CacheableListModelSaver(ModelSaver<T> modelSaver, CacheAdapter<T> cacheAdapter){
        super(modelSaver);
        this.cacheAdapter = cacheAdapter;
    }

    @Override
    public synchronized long saveAll(Collection<T> tableCollection, DatabaseWrapper wrapper) {
        return applyAndCount(tableCollection,
               modelAdapter.getSaveStatement(wrapper), model -> {
                    cacheAdapter.storeModelInCache(model);
               return null;
               }, (model, databaseStatement) -> modelSaver.save(model, databaseStatement, wrapper));

    }

    @Override
    public synchronized long insertAll(Collection<T> tableCollection, DatabaseWrapper wrapper) {
        return applyAndCount(tableCollection,
                modelAdapter.getInsertStatement(wrapper), model -> {
                    cacheAdapter.storeModelInCache(model);
                    return null;
                }, (model, databaseStatement) ->  modelSaver.insert(model, databaseStatement, wrapper) > 0);
    }

    @Override
    public synchronized long updateAll(Collection<T> tableCollection, DatabaseWrapper wrapper) {
        return applyAndCount(tableCollection,
                modelAdapter.getUpdateStatement(wrapper), model -> {
                    cacheAdapter.storeModelInCache(model);
                    return null;
                }, (model, databaseStatement) ->  modelSaver.update(model, databaseStatement, wrapper));
    }

    @Override
    public synchronized long deleteAll(Collection<T> tableCollection, DatabaseWrapper wrapper) {
        return applyAndCount(tableCollection,
                modelAdapter.getDeleteStatement(wrapper), model -> {
                    cacheAdapter.removeModelFromCache(model);
                    return null;
                }, (model, databaseStatement) ->  modelSaver.delete(model, databaseStatement, wrapper));
    }

    private long applyAndCount(Collection<T> tableCollection,
                               DatabaseStatement databaseStatement,
                               Function<T, Void> cacheFn,
                               BiFunction<T, DatabaseStatement, Boolean> fn) {
        // skip if empty.
        if (tableCollection.isEmpty()) {
            return 0L;
        }

        AtomicLong count = new AtomicLong(0L);

        tableCollection.forEach(model -> {
            if (fn.apply(model, databaseStatement)) {
                cacheFn.apply(model);
                count.getAndIncrement();
            }
        });
        return count.get();
    }
}
